﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Text;
using Raven.Server;
using Raven.Server.Extensions;
using Raven.Server.Routing;
using Raven.Server.Web;

namespace TypingsGenerator
{
    public class EndpointsExporter
    {
        private readonly Dictionary<string, Dictionary<string, string>> _globalEndpoints = new();
        private readonly Dictionary<string, Dictionary<string, string>> _databaseEndpoints = new();

        private const string DatabaseEndpointPrefix = "/databases/*";
        private const string TargetFile = "endpoints.ts";

        public void Create(string targetDir)
        {
            ScanAssembly(typeof(RavenServer).Assembly);

            WriteEndpointsFile(targetDir);
        }

        private void WriteEndpointsFile(string targetDir)
        {

            var builder = new StringBuilder();
            builder.AppendLine("// This class is autogenerated. Do NOT modify");
            builder.AppendLine("class endpointConstants {");

            WriteGroup(_globalEndpoints, builder, "global");
            WriteGroup(_databaseEndpoints, builder, "databases");

            builder.AppendLine("}");
            builder.AppendLine();
            builder.AppendLine("export = endpointConstants;");
            
            File.WriteAllText(Path.Combine(targetDir, TargetFile), builder.ToString());
        }

        private void WriteGroup(Dictionary<string, Dictionary<string, string>> endpoints, StringBuilder builder, string groupName)
        {
            builder.AppendLine("    static " + groupName + " = { ");

            var handlersMapSize = endpoints.Count;
            var currentHandlerMap = 0;
            
            foreach (var kvp in endpoints)
            {
                currentHandlerMap++;
                var handlerSeparator = handlersMapSize == currentHandlerMap ? "" : ",";

                builder.AppendLine("        " + kvp.Key + ": {");

                var urlsLength = kvp.Value.Count;
                var currentUrl = 0;
                
                foreach (var urlKvp in kvp.Value)
                {
                    currentUrl++;
                    var urlSeparator = urlsLength == currentUrl ? "" : ",";
                    var keyEscaped = (char.IsDigit(urlKvp.Key[0]) ? "_" : string.Empty) + urlKvp.Key;

                    builder.AppendLine("            " + keyEscaped + ": \"" + urlKvp.Value + "\"" + urlSeparator);
                }

                builder.AppendLine("        }" + handlerSeparator);
            }

            builder.AppendLine("    }");
        }

        private void ScanAssembly(Assembly assembly)
        {
            foreach (Type type in assembly.GetTypes())
            {
                if (type.IsSubclassOf(typeof(RequestHandler)))
                {
                    foreach (MethodInfo methodInfo in type.GetMethods())
                    {
                        foreach (var actionAttribute in methodInfo.GetCustomAttributes<RavenActionAttribute>())
                        {
                            RegisterEndpoint(actionAttribute, methodInfo);
                        }
                    }
                }
            }
        }

        private void RegisterEndpoint(RavenActionAttribute actionAttribute, MethodInfo methodInfo)
        {
            var path = actionAttribute.Path;
            var dbEndpoint = path.StartsWith(DatabaseEndpointPrefix);
            var target = dbEndpoint ? _databaseEndpoints : _globalEndpoints;
            var name = methodInfo.DeclaringType.Name;

            var handlerIdx = name.IndexOf("Handler");
            if (handlerIdx == -1)
            {
                throw new ArgumentException("Invalid Handler name: " + name);
            }
           
            var handlerKey = name.Substring(0, handlerIdx);

            var handlerLowerCased = LowerCaseFirst(handlerKey);

            var perHandlerDict = target.GetOrAdd(handlerLowerCased);

            var action = actionAttribute.Path;

            if (action.StartsWith(DatabaseEndpointPrefix))
            {
                action = action.Substring(DatabaseEndpointPrefix.Length);
            }

            if (action == "/")
            {
                return;
            }
            
            var fieldName = UrlToFieldName(action);

            perHandlerDict[fieldName] = action.TrimEnd('$');
        }

        private static string UrlToFieldName(string input)
        {
            var buffer = "";
            var capitalizeNext = false;
            for (var i = 0; i < input.Length; i++)
            {
                var c = input[i];
                
                if (c == '.')
                {
                    buffer += "_";
                    continue;
                }

                if (c == '/' || c == '-')
                {
                    capitalizeNext = true;
                }
                else
                {
                    if (capitalizeNext)
                    {
                        buffer += c.ToString().ToUpper(CultureInfo.InvariantCulture);
                        capitalizeNext = false;
                    }
                    else
                    {
                        buffer += c;
                    }
                }
            }

            return LowerCaseFirst(buffer);
        }
        
        private static string LowerCaseFirst(string input)
        {
            return input[0].ToString().ToLower(CultureInfo.InvariantCulture) + input.Substring(1);
        }

    }
}
